using System;
using System.Drawing;
using System.Collections.Generic;
using Clandestine.Graphics;

namespace Clandestine.Engines.Field
{
	public class MegaSpriteLayerGroup : LayerGroup
	{
		private bool allDirty = true; // if this is set, dirtyTiles is ignored and all tiles are treated as dirty.
		private List<Point> dirtyTiles = new List<Point>(); // so we know exactly which have been marked dirty since the last paint!.
		private Dictionary<int, Dictionary<int, Sprite>> sprites = new Dictionary<int, Dictionary<int, Sprite>>();
		private const int spriteSize = 5; // how many tiles each sprite represents, eg. 5x5
		public override Point Position 
		{
			get { return base.Position; }
			set 
			{
				base.Position = value; 
				this.positionSprite(this.MinX, this.MinY); 
			}
		}
		
		private int depth = 0;
		public override int Depth
		{
			get { return depth; }
			set
			{
				this.changeSpriteDepth(depth);
				depth = value;
			}
		}
	
		public MegaSpriteLayerGroup(RenderList r, Map map, object parent) : base(r, map, parent)
		{
			
		}
		
		public override List<Renderable> GetRenderables()
		{
			throw new NotImplementedException("This would be better as IEnumerable<>");
		}
		
		public override void RedrawDirty ()
		{
			if (this.Dirty)
				this.redrawSprite();	
			this.Dirty = false;	
		}
		
		private void changeSpriteDepth(int depth)
		{
			foreach (Dictionary<int, Sprite> y in this.sprites.Values)
				foreach(Sprite s in y.Values)
					s.Depth = depth;
		}
		
		private void changeSpriteVisibility(bool isvisible)
		{
			foreach (Dictionary<int, Sprite> y in this.sprites.Values)
				foreach(Sprite s in y.Values)
					s.Visible = isvisible;
		}
		
		private void redrawSprite()
		{
			if (!this.Visible)
			{
				changeSpriteVisibility(false);
				return;
			}
				
			int minX = this.MinX;
			int minY = this.MinY;
			int maxX = this.MaxX;
			int maxY = this.MaxY;	
			
			// Redraw all or a subset of the MegaSprites?
			lock (dirtyTiles) // race condition
			{
				if (allDirty)
				{
					// Redraw all
					for (int spriteX = minX; spriteX <= maxX; spriteX += spriteSize)
						for (int spriteY = minY; spriteY <= maxY; spriteY += spriteSize)
							redrawMegaSprite(spriteX, spriteY);
					allDirty = false;
					dirtyTiles.Clear(); // they've all been cleaned up.
				}
				else
				{
					// Redraw some. Remember which we've redrawn so we dont repeatedly redraw the same one.
					List<Point> redrawn = new List<Point>();
					foreach (Point p in dirtyTiles)
					{
						int spriteX = (p.X  / spriteSize) * spriteSize;
						int spriteY = (p.Y / spriteSize) * spriteSize;
						
						// check we've not already redrawn.
						if (redrawn.IndexOf(new Point(spriteX, spriteY)) < 0)
						{
							redrawMegaSprite(spriteX, spriteY);
							redrawn.Add(new Point(spriteX, spriteY));
						}
					}
				}
			}
	
		}
		
		// spriteX, spriteY are upper-left tile co-ord of the first tile in the MegaSprite.
		private void redrawMegaSprite(int spriteX, int spriteY)
		{
			// Make a renderlist
			RenderList r = new RenderList(this.Graphics);
			
			// Render this mega-sprite
			for (int x = 0; x < spriteSize; x++)
				for (int y = 0; y < spriteSize; y++)
					for (int depth = 0; depth < this.Layers.Count; depth++)
						if (this.Layers[depth].Visible)
						{	
							// Only draw the real tile if there isn't a temporary one to preview here! -- More accurate previews.
							Tile? tmp = Layers[depth].GetTemporaryTile(spriteX + x, spriteY + y);
							if (tmp == null)
								this.drawTile(Layers[depth].GetTile(spriteX + x, spriteY + y), r, depth, x, y);
							else
								this.drawTile(tmp, r, depth, x, y);
						}
		
			// Renderlist empty? Means there are no tiles in this MegaSprite. If so, delete any existing MegaSprite and return.
			if (r.Count == 0)
			{
				if (sprites.ContainsKey(spriteY) && sprites[spriteY].ContainsKey(spriteX))
				{
					sprites[spriteY][spriteX].Dispose();
					sprites[spriteY].Remove(spriteX);
				}
				r.Dispose();
				return;
			}
				
			// Make the texture
			string identifier = System.Guid.NewGuid().ToString();
			Texture tex = this.Graphics.RenderToTexture(r, new Size((spriteSize+1) * this.Map.GridSize, (spriteSize+1) * this.Map.GridSize), identifier);
			
			// Cleanup renderlist
			r.Dispose();
			
			// Make sprite
			Sprite s = new Sprite(this.RenderList, tex);
			s.Depth = this.depth;
			s.Transform.X = (this.Position.X + spriteX) * this.Map.GridSize;
			s.Transform.Y = (this.Position.Y + spriteY) * this.Map.GridSize;	
			
			// Add sprite to our dicts
			if (!sprites.ContainsKey(spriteY))
				sprites.Add(spriteY, new Dictionary<int, Sprite>());
			if (!sprites[spriteY].ContainsKey(spriteX))
				sprites[spriteY].Add(spriteX, s);
			else
			{
				if (sprites[spriteY][spriteX] != null)
					sprites[spriteY][spriteX].Dispose();
				sprites[spriteY][spriteX] = s;
			}
		}
		
		private void positionSprite(int minX, int minY)
		{
			foreach (KeyValuePair<int, Dictionary<int, Sprite>> kvpY in sprites)
				foreach (KeyValuePair<int, Sprite> kvpX in kvpY.Value)
				{
					kvpX.Value.Transform.X = (this.Position.X + kvpX.Key) * this.Map.GridSize;
					kvpX.Value.Transform.Y = (this.Position.Y + kvpY.Key) * this.Map.GridSize;	
				}		
		}
	
		private void drawTile(Tile? t, RenderList r, decimal depth, int x, int y)
		{
			if (t != null) 
			{ 
				Sprite s = new Sprite(r, t.Value.Texture);
				s.Depth = depth;
				s.Transform.X = x * base.Map.GridSize;
				s.Transform.Y = y * base.Map.GridSize; 
			}
			// doesn't matter that Sprite goes out of scope, still in render list!
		}
		
		public override void Dispose ()
		{
			foreach (Dictionary<int, Sprite> ys in this.sprites.Values)
				foreach (Sprite xs in ys.Values)
					xs.Dispose();
			base.Dispose();
		}
		
		protected override void OnTileChange(int depth, int x, int y)
		{
			this.Dirty = true;
			lock (dirtyTiles)
				dirtyTiles.Add(new Point(x, y));
		}
		
		protected override void OnVisibilityChange()
		{
			this.Dirty = true; // DOES THIS NEED TO MAKE US DIRTY?
			Clandestine.Base.Log.w("CHECK ME");
		}
	}
}

